<!doctype html>
<html>
<head>
  <meta charset='utf-8'>
  <title>Custom HTML in cells and headers - Handsontable</title>

  <!--
  Loading Handsontable (full distribution that includes all dependencies)
  -->
  <script data-jsfiddle="common" src="../dist/handsontable.full.js"></script>
  <link data-jsfiddle="common" rel="stylesheet" media="screen" href="../dist/handsontable.full.css">

  <!--
  Loading demo dependencies. They are used here only to enhance the examples on this page
  -->
  <link data-jsfiddle="common" rel="stylesheet" media="screen" href="css/samples.css?20140331">
  <script src="js/samples.js"></script>
  <script src="js/highlight/highlight.pack.js"></script>
  <link rel="stylesheet" media="screen" href="js/highlight/styles/github.css">
  <link rel="stylesheet" href="css/font-awesome/css/font-awesome.min.css">

  <!--
  Facebook open graph. Don't copy this to your project :)
  -->
  <meta property="og:title" content="Custom HTML in cells and headers">
  <meta property="og:description"
        content="This example shows how to use custom cell renderers to display HTML content in a cell and header.">
  <meta property="og:url" content="http://handsontable.com/demo/reference_html.html">
  <meta property="og:image" content="http://handsontable.com/demo/image/og-image.png">
  <meta property="og:image:type" content="image/png">
  <meta property="og:image:width" content="409">
  <meta property="og:image:height" content="164">
  <link rel="canonical" href="http://handsontable.com/demo/reference_html.html">

  <!--
  Google Analytics for GitHub Page. Don't copy this to your project :)
  -->
  <script src="js/ga.js"></script>
</head>

<body>


<div class="wrapper">
<div class="wrapper-row">
<div id="global-menu-clone">
  <h1><a href="../index.html">Handsontable</a></h1>

</div>

<div id="container">
<div class="columnLayout">

<div class="rowLayout">
  <div class="descLayout">
    <div class="pad">
      <h2>Custom HTML in cells and headers</h2>

      <p>On this page:</p>

      <ul>
        <li><a href="#cell">Rendering custom HTML in cells</a></li>
        <li><a href="#header">Rendering custom HTML in header</a></li>
        <li><a href="#dropdown">Changing cell type from a dropdown menu in cell header</a></li>
      </ul>
    </div>
  </div>
</div>

<div class="rowLayout">
  <div class="descLayout">
    <div class="pad" data-jsfiddle="example1">
      <a name="cell"></a>

      <h2>Rendering custom HTML in cells</h2>

      <p>This example shows how to use custom cell renderers to display HTML content in a cell.</p>

      <p>This is a very powerful feature. Just remember to escape any HTML code that could be used for XSS
        attacks.</p>

      <p>In the below configuration:</p>

      <ul>
        <li><strong>Title</strong> column uses built-in HTML renderer that allows any HTML. This is unsafe if your code comes from untrusted source. Take notice that a Handsontable user can use it to enter <code>&lt;script&gt;</code> or other potentially malicious tags using the cell editor!</li>
        <li><strong>Description</strong> column also uses HTML renderer (same as above)</li>
        <li><strong>Comments</strong> column uses a custom renderer (<code>safeHtmlRenderer</code>). This should be safe for user input, because only certain tags are allowed</li>
        <li><strong>Cover</strong> column accepts image URL as a string and converts it to a <code>&lt;img&gt;</code> in the renderer</li>
      </ul>

      <div id="example1"></div>

      <p>
        <button name="dump" data-dump="#example1" data-instance="hot1" title="Prints current data source to Firebug/Chrome Dev Tools">
          Dump data to console
        </button>
      </p>
    </div>
  </div>

  <div class="codeLayout">
    <div class="pad">
      <div class="jsFiddle">
        <button class="jsFiddleLink" data-runfiddle="example1">Edit in jsFiddle</button>
      </div>

      <script data-jsfiddle="example1">
        var data = [
            {
              title: "<a href='http://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691'>Professional JavaScript for Web Developers</a>",
              description: "This <a href='http://bit.ly/sM1bDf'>book</a> provides a developer-level introduction along with more advanced and useful features of <b>JavaScript</b>.",
              comments: "I would rate it &#x2605;&#x2605;&#x2605;&#x2605;&#x2606;",
              cover: "http://ecx.images-amazon.com/images/I/51bRhyVTVGL._SL50_.jpg"
            },
            {
              title: "<a href='http://shop.oreilly.com/product/9780596517748.do'>JavaScript: The Good Parts</a>",
              description: "This book provides a developer-level introduction along with <b>more advanced</b> and useful features of JavaScript.",
              comments: "This is <big>the</big> book about JavaScript",
              cover: "http://ecx.images-amazon.com/images/I/51gdVAEfPUL._SL50_.jpg"
            },
            {
              title: "<a href='http://shop.oreilly.com/product/9780596805531.do'>JavaScript: The Definitive Guide</a>",
              description: "<em>JavaScript: The Definitive Guide</em> provides a thorough description of the core <b>JavaScript</b> language and both the legacy and standard DOMs implemented in web browsers.",
              comments: "I've never actually read it, but the <a href='http://shop.oreilly.com/product/9780596805531.do'>comments</a> are highly <strong>positive</strong>.",
              cover: "http://ecx.images-amazon.com/images/I/51VFNL4T7kL._SL50_.jpg"
            }
          ],
          container1,
          hot1;

        container1 = document.getElementById('example1');
        hot1 = new Handsontable(container1, {
          data: data,
          colWidths: [200, 200, 200, 60],
          colHeaders: ["Title", "Description", "Comments", "Cover"],
          columns: [
            {data: "title", renderer: "html"},
            {data: "description", renderer: "html"},
            {data: "comments", renderer: safeHtmlRenderer},
            {data: "cover", renderer: coverRenderer}
          ]
        });

        // original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        function strip_tags(input, allowed) {
          var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
            commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;

          // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
          allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');

          return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
            return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
          });
        }

        function safeHtmlRenderer(instance, td, row, col, prop, value, cellProperties) {
          var escaped = Handsontable.helper.stringify(value);
          escaped = strip_tags(escaped, '<em><b><strong><a><big>'); //be sure you only allow certain HTML tags to avoid XSS threats (you should also remove unwanted HTML attributes)
          td.innerHTML = escaped;

          return td;
        }

        function coverRenderer (instance, td, row, col, prop, value, cellProperties) {
          var escaped = Handsontable.helper.stringify(value),
            img;

          if (escaped.indexOf('http') === 0) {
            img = document.createElement('IMG');
            img.src = value;

            Handsontable.Dom.addEvent(img, 'mousedown', function (e){
              e.preventDefault(); // prevent selection quirk
            });

            Handsontable.Dom.empty(td);
            td.appendChild(img);
          }
          else {
            // render as text
            Handsontable.renderers.TextRenderer.apply(this, arguments);
          }

          return td;
        }
      </script>
    </div>
  </div>
</div>

<div class="rowLayout">
  <div class="descLayout">
    <div class="pad" data-jsfiddle="example2">
      <a name="header"></a>

      <h2>Rendering custom HTML in header</h2>

      <p>You can also put HTML into row and column headers.</p>

      <p>If you need to attach events to DOM elements like the checkbox below, just remember to identify the element
        by class name, not by id. This is because row and column headers are duplicated in the DOM tree and id
        attribute must be unique.</p>

      <div id="example2"></div>

      <p>
        <button name="dump" data-dump="#example2" data-instance="hot2" title="Prints current data source to Firebug/Chrome Dev Tools">
          Dump data to console
        </button>
      </p>
    </div>
  </div>

  <div class="codeLayout">
    <div class="pad">
      <div class="jsFiddle">
        <button class="jsFiddleLink" data-runfiddle="example2">Edit in jsFiddle</button>
      </div>

      <script data-jsfiddle="example2">
        var
          isChecked,
          container2 = document.getElementById('example2'),
          hot2;

          hot2 = new Handsontable(container2, {
            startCols: 2,
            columns: [
              {},
              {renderer: customRenderer}
            ],
            colHeaders: function (col) {
              var txt;

              switch (col) {
                case 0:
                  return '"<b>Bold</b> and <em>Beautiful</em>';

                case 1:
                  txt = "Some <input type='checkbox' class='checker' ";
                  txt += isChecked ? 'checked="checked"' : '';
                  txt += "> checkbox";

                  return txt;
              }
            }
          });

          function customRenderer(instance, td) {
            Handsontable.renderers.TextRenderer.apply(this, arguments);

            if (isChecked) {
              td.style.backgroundColor = 'yellow';
            }
            else {
              td.style.backgroundColor = 'white';
            }

            return td;
          }

          Handsontable.Dom.addEvent(container, 'mousedown', function (event) {
            if (event.target.nodeName == 'INPUT' && event.target.className == 'checker') {
              event.stopPropagation();
            }
          });

          Handsontable.Dom.addEvent(container, 'mouseup', function (event) {
            if (event.target.nodeName == 'INPUT' && event.target.className == 'checker') {
              isChecked = !event.target.checked;
              hot2.render();
            }
          });
      </script>
    </div>
  </div>
</div>

<div class="rowLayout">
  <div class="descLayout">
    <div class="pad" data-jsfiddle="example2">
      <a name="dropdown"></a>

      <h2>Changing cell type from a dropdown menu in cell header</h2>

      <p>This example makes use of a plugin hook to add a custom dropdown menu to the cell header</p>

      <div id="example3"></div>

      <p>
        <button name="dump" data-dump="#example3" data-instance="hot3" title="Prints current data source to Firebug/Chrome Dev Tools">
          Dump data to console
        </button>
      </p>
    </div>
  </div>

  <div class="codeLayout">
    <div class="pad">
      <div class="jsFiddle">
        <button class="jsFiddleLink" data-runfiddle="example2">Edit in jsFiddle</button>
      </div>

      <style data-jsfiddle="example3">
        .changeType {
          border: 1px solid #bbb;
          color: #bbb;
          background: #eee;
          border-radius: 2px;
          padding: 2px;
          font-size: 9px;

          float: right;
          line-height: 9px;
          margin: 3px 3px 0 0;
        }

        .changeType:hover {
          border: 1px solid #777;
          color: #777;
          cursor: pointer;
        }

        .changeType.pressed {
          background-color: #999;
        }

        .changeTypeMenu {
          position: absolute;
          border: 1px solid #ccc;
          margin-top: 18px;
          box-shadow: 0 1px 3px -1px #323232;
          background: white;
          padding: 0;
          display: none;
          z-index: 10;
        }

        .changeTypeMenu li {
          text-align: left;
          list-style: none;
          padding: 2px 20px;
          cursor: pointer;
          margin-bottom: 0;
        }

        .changeTypeMenu li.active:before {
          content: "\2714";
          margin-left: -15px;
          margin-right: 3px;
        }

        .changeTypeMenu li:hover {
          background: #eee;
        }


      </style>

      <script data-jsfiddle="example3">
        var
          data3 = [
            ['', 'Maserati', 'Mazda', 'Mercedes', 'Mini', 'Mitsubishi'],
            ['2009', 0, 2941, 4303, 354, 5814],
            ['2010', 3, 2905, 2867, 412, 5284],
            ['2011', 4, 2517, 4822, 552, 6127],
            ['2012', 2, 2422, 5399, 776, 4151]
          ],
          columns = [
            {type: 'numeric'},
            {type: 'numeric'},
            {type: 'numeric'},
            {type: 'numeric'},
            {type: 'numeric'},
            {type: 'numeric'}
          ],
          container = document.getElementById('example3'),
          hot3;

        hot3 = new Handsontable(container,{
          data: data3,
          colHeaders: true,
          minSpareRows: 1,
          type: 'numeric',
          columns: columns,

          afterGetColHeader: function (col, TH) {
            var instance = this,
              menu = buildMenu(columns[col].type),
              button = buildButton();

            addButtonMenuEvent(button, menu);

            Handsontable.Dom.addEvent(menu, 'click', function (event) {
              if (event.target.nodeName == 'LI') {
                setColumnType(col, event.target.data['colType'], instance);
              }
            });
            TH.firstChild.appendChild(button);
          },
          cells: function (row, col, prop) {
            var cellProperties;

            if (row === 0) {
              cellProperties = {
                type: 'text' // force text type for first row
              };

              return cellProperties;
            }
          }
        });

        function addButtonMenuEvent(button, menu) {
          Handsontable.Dom.addEvent(button, 'click', function (event) {
            var changeTypeMenu, position, removeMenu;

            document.body.appendChild(menu);

            event.preventDefault();
            event.stopImmediatePropagation();

            changeTypeMenu = document.querySelectorAll('.changeTypeMenu');

            for (var i = 0, len = changeTypeMenu.length; i < len; i++) {
              changeTypeMenu[i].style.display = 'none';
            }
            menu.style.display = 'block';
            position = button.getBoundingClientRect();

            menu.style.top = (position.top + (window.scrollY || window.pageYOffset)) + 2 + 'px';
            menu.style.left = (position.left) + 'px';

            removeMenu = function (event) {
              if (event.target.nodeName == 'LI' && event.target.parentNode.className.indexOf('changeTypeMenu') !== -1) {
                if (menu.parentNode) {
                  menu.parentNode.removeChild(menu);
                }
              }
            };
            Handsontable.Dom.removeEvent(document, 'click', removeMenu);
            Handsontable.Dom.addEvent(document, 'click', removeMenu);
          });
        }

        function buildMenu(activeCellType){
          var
            menu = document.createElement('UL'),
            types = ['text', 'numeric', 'date'],
            item;

          menu.className = 'changeTypeMenu';

          for (var i = 0, len = types.length; i< len; i++) {
            item = document.createElement('LI');
            item.innerText = types[i];
            item.data = {'colType': types[i]};

            if (activeCellType == types[i]) {
              item.className = 'active';
            }
            menu.appendChild(item);
          }

          return menu;
        }

        function buildButton() {
          var button = document.createElement('BUTTON');

          button.innerHTML = '\u25BC';
          button.className = 'changeType';

          return button;
        }

        function setColumnType(i, type, instance) {
          columns[i].type = type;
          instance.updateSettings({columns: columns});
          instance.validateCells(function() {
            instance.render();
          });
        }
      </script>
    </div>
  </div>
</div>

<div class="footer-text">Handsontable &copy; 2012-2014 Marcin Warpechowski and contributors.<br> Code and documentation
  licensed under the The MIT License.
</div>
</div>

</div>

</div>
</div>

<div id="outside-links-wrapper"></div>

</body>
</html>
